/**
 * Copyright 2006 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.ocean.main.util;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.ObjectUtils;

/**
 * A simple container class for modeling an ordered list of name/value pairs.
 *
 * <p>
 * Unlike Maps:
 * </p>
 * <ul>
 *  <li>Names may be repeated</li>
 *  <li>Order of elements is maintained</li>
 *  <li>Elements may be accessed by numeric index</li>
 *  <li>Names and Values can both be null</li>
 * </ul>
 *
 * <p>
 *
 */
public class NamedList<K,V> implements Cloneable, Serializable {
  public List<Entry<K,V>> entries;
  public boolean nameLowerCase = false;
  
  public NamedList(Map<K,V> map) {
    entries = new ArrayList<Entry<K,V>>(map.size());
    for (Map.Entry<K,V> entry : map.entrySet()) {
      add(entry.getKey(), entry.getValue());
    }
  }
  
  public NamedList() {
    entries = new ArrayList<Entry<K,V>>();
  }
  
  public NamedList(List<Entry<K,V>> entries) {
    this.entries = new ArrayList<Entry<K,V>>(entries);
  }
  
  public NamedList(int size) {
    entries = new ArrayList<Entry<K,V>>(size);
  }
  
  public Map.Entry<K,MutableInt> getMax(LinkedHashMap<K,MutableInt> countMap) {
    if (countMap.size() == 0) return null;
    Map.Entry<K,MutableInt> maxEntry = null;
    for (Map.Entry<K,MutableInt> entry : countMap.entrySet()) {
      if (maxEntry == null) maxEntry = entry;
      else {
        if (entry.getValue().value > maxEntry.getValue().value) {
          maxEntry = entry;
        }
      }
    }
    return maxEntry;
  }
  
  public LinkedHashMap<K,MutableInt> countKeys() {
    LinkedHashMap<K,MutableInt> countMap = new LinkedHashMap<K,MutableInt>();
    for (Entry<K,V> entry : getEntries()) {
      MutableInt mutableInt = countMap.get(entry.key);
      if (mutableInt == null) countMap.put(entry.key, new MutableInt(1));
      else mutableInt.value++;
    }
    return countMap;
  }
  
  public List<Entry<K,V>> getEntries() {
    return entries;
  }
  
  public Iterator<Entry<K,V>> iterator() {
    return entries.iterator();
  }
  
  public static class Entry<K,V> {
    public K key;
    public V value;
    
    public Entry(K key, V value) {
      this.key = key;
      this.value = value;
    }
    
    public boolean keyMatches(Object other) {
      if (key == null && other == null) return true;
      else return ObjectUtils.equals(other, key);
    }
  }
  
  /** Creates an empty instance */
  public NamedList(boolean nameLowerCase) {
    this.nameLowerCase = nameLowerCase;
    entries = new ArrayList<Entry<K,V>>();
  }
  
  public void clear() {
    entries.clear();
  }
  
  public List<V> getValues() {
    List<V> values = new ArrayList<V>(size());
    for (Entry<K,V> entry : entries) {
      values.add(entry.value);
    }
    return values;
  }
  
  public List<K> getFields() {
    List<K> fields = new ArrayList<K>(size());
    for (Entry<K,V> entry : entries) {
      fields.add(entry.key);
    }
    return fields;
  }
  
  public K toLowerCase(K object) {
    if (object.getClass().isAssignableFrom(String.class) && nameLowerCase) {
      String string = (String)object;
      object = (K)string.toLowerCase();
      return object;
    }
    return object;
  }
  
  public List<V> getValues(K key) {
    List<V> values = new ArrayList<V>(3);
    for (Entry<K,V> entry : entries) {
      if (entry.keyMatches(key)) {
        values.add(entry.value);
      }
    }
    return values;
  }
  
  /** The total number of name/value pairs */
  public int size() {
    return entries.size();
  }
  
  /**
   * The name of the pair at the specified List index
   *
   * @return null if no name exists
   */
  public K getName(int index) {
    Entry<K,V> entry = entries.get(index);
    return entry.key;
  }
  
  public V get(int idx) {
    return getValue(idx);
  }
  
  /**
   * The value of the pair at the specified List index
   *
   * @return may be null
   */
  public V getValue(int index) {
    Entry<K,V> entry = entries.get(index);
    if (entry != null) return entry.value;
    return null;
  }
  
  /**
   * Adds a name/value pair to the end of the list.
   */
  public void add(K key, V value) {
    key = toLowerCase(key);
    Entry<K,V> entry = new Entry<K,V>(key, value);
    entries.add(entry);
  }
  
  public boolean set(K key, V value) {
  	int index = indexOf(key, 0);
  	if (index == -1) return false;
  	Entry<K,V> entry = new Entry<K,V>(key, value);
  	entries.set(index, entry);
  	return true;
  }
  
  /**
   * Modifies the name of the pair at the specified index.
   */
  public void setKey(int index, K key) {
    Entry<K,V> entry = entries.get(index);
    if (entry != null) entry.key = key;
  }
  
  /**
   * Modifies the value of the pair at the specified index.
   */
  public void setValue(int index, V value) {
    Entry<K,V> entry = entries.get(index);
    if (entry != null) entry.value = value;
  }
  
  public void remove(K key) {
    int index = indexOf(key, 0);
    if (index != -1) entries.remove(index);
  }
  
  /**
   * Scans the list sequentially beginning at the specified index and
   * returns the index of the first pair with the specified name.
   *
   * @param name name to look for, may be null
   * @param start index to begin searching from
   * @return The index of the first matching pair, -1 if no match
   */
  public int indexOf(K key, int start) {
    int count = 0;
    for (Entry<K,V> entry : entries) {
      if (entry.keyMatches(key)) return count;
      count++;
    }
    return -1;
  }
  
  /**
   * Gets the value for the first instance of the specified name
   * found.
   *
   * @return null if not found or if the value stored was null.
   * @see #indexOf
   * @see #get(String,int)
   */
  public V get(K key) {
    key = toLowerCase(key);
    return get(key, 0);
  }
  
  /**
   * Gets the value for the first instance of the specified name
   * found starting at the specified index.
   *
   * @return null if not found or if the value stored was null.
   * @see #indexOf
   */
  public V get(K key, int start) {
    key = toLowerCase(key);
    int sz = size();
    for (int i=start; i < sz; i++) {
      Entry<K,V> entry = entries.get(i);
      if (entry != null && ObjectUtils.equals(entry.key, key)) return entry.value;
    }
    return null;
  }
  
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append('{');
    int sz = size();
    for (int i=0; i<sz; i++) {
      if (i != 0) sb.append(',');
      sb.append(getName(i));
      sb.append('=');
      sb.append(getValue(i));
    }
    sb.append('}');
    
    return sb.toString();
  }
  
  public boolean addAll(Map<K,V> map) {
    for (Map.Entry<K,V> entry : map.entrySet()) {
      add(entry.getKey(), entry.getValue());
    }
    return map.size() > 0;
  }
  
  /** Appends the elements of the given NamedList to this one. */
  public boolean addAll(NamedList namedList) {
    entries.addAll(namedList.entries);
    return namedList.size() > 0;
  }
  
  /**
   * Makes a <i>shallow copy</i> of the named list.
   */
  public NamedList<K,V> clone() {
    ArrayList<Entry<K,V>> newList = new ArrayList<Entry<K,V>>(entries.size());
    newList.addAll(entries);
    return new NamedList<K,V>(newList);
  }
}
